using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics.Contracts;

namespace Konst3d.Efficiency.IO
{
    public abstract class UnorderedListFile<TEntry>: BinaryFile // IDListFile ??? // IEnumerable<TEntry> - enumeration by list of IDs...
    {
        #region Abstract Members

        protected abstract long CapacityOffset
        {
            get;
        }

        protected abstract long DataOffset
        {
            get;
        }

        protected abstract int EntryByteSize
        {
            get;
        }

        protected abstract TEntry ReadData(long offset = -1, SeekOrigin origin = SeekOrigin.Begin);
        protected abstract void WriteData(TEntry entry, long offset = -1, SeekOrigin origin = SeekOrigin.Begin);

        #endregion // Abstract Members

        #region Properties

        public int Count
        {
            get
            {
                return _capacity - _empty.Count;
            }
        }

        public int Capacity
        {
            get
            {
                return _capacity;
            }
            private set
            {
                _writer.Write(value, (int)CapacityOffset);
                _capacity = value;
            }
        }

        public TEntry this[int index]
        {
            get
            {
                Contract.Requires(index >= 0 && index < _capacity);
                Contract.Requires(!_empty.Contains(index));

                return GetEntry(index);
            }
            set
            {
                Contract.Requires(index >= 0 && index < _capacity);

                WriteEntry(value, GetEntryOffset(index));

                if (_empty.Contains(index))
                {
                    _empty.Remove(index);
                }
            }
        }

        #endregion // Properties

        #region Constructor

        protected UnorderedListFile(FileStream stream, Encoding encoding) :
            this(stream, new BinaryReader(stream, encoding), new BinaryWriter(stream, encoding))
        {
        }

        protected UnorderedListFile(FileStream stream, BinaryReader reader, BinaryWriter writer) :
            base(stream, reader, writer)
        {
            BuildEmptyList();
        }

        protected UnorderedListFile(FileStream stream, BinaryReader reader, BinaryWriter writer, uint signature, uint version) :
            base(stream, reader, writer, signature, version)
        {
            BuildEmptyList();
        }

        #endregion // Constructor

        #region Public Methods

        public virtual IDictionary<int, TEntry> GetAll()
        {
            var all = new Dictionary<int, TEntry>();

            for (int i = 0; i < _capacity; ++i)
            {
                if (!_empty.Contains(i))
                {
                    all.Add(i, GetEntry(i));
                }
            }

            return all;
        }

        public virtual TEntry GetEntry(int id)
        {
            return ReadEntry(GetEntryOffset(id));
        }

        public virtual int AddEntry(TEntry entry)
        {
            Contract.Assert(_capacity < int.MaxValue);

            int id = 0;

            if (_empty.Count > 0)
            {
                id = _empty[0];
                WriteEntry(entry, GetEntryOffset(id), SeekOrigin.Begin);
                _empty.RemoveAt(0);
            }
            else
            {
                WriteEntry(entry, 0, SeekOrigin.End);
                id = Capacity++;
            }

            return id;
        }

        public virtual void RemoveEntry(int id)
        {
            Contract.Requires(id >= 0 && id < _capacity);
            Contract.Requires(!_empty.Contains(id));

            WriteEntryFlag(id, -1);
            _empty.Add(id);
        }

        #endregion // Public Methods

        #region Protected Methods

        protected long GetEntryDataOffset(int id)
        {
            return GetEntryOffset(id) + sizeof(int);
        }

        #endregion // Protected Methods

        #region Private Methods

        private void BuildEmptyList()
        {
            _capacity = _reader.ReadInt32(CapacityOffset);

            for (int i = 0; i < _capacity; ++i)
            {
                if (ReadEntryFlag(i) < 0)
                {
                    _empty.Add(i);
                }
            }
        }

        private void WriteEntry(TEntry entry, long offset = -1, SeekOrigin origin = SeekOrigin.Begin)
        {
            if (offset >= 0)
            {
                _writer.Seek((int)offset, origin);
            }

            _writer.Write((int)1); // used flag

            WriteData(entry);
        }

        private TEntry ReadEntry(long offset = -1, SeekOrigin origin = SeekOrigin.Begin)
        {
            if (offset >= 0)
            {
                _reader.BaseStream.Seek(offset + sizeof(int), origin); // + sizeof(int) - to skip the flag and seek write to data
            }

            // int flag = _reader.ReadInt32();

            return ReadData();
        }

        private int ReadEntryFlag(int id)
        {
            Contract.Requires(id < _capacity);

            return _reader.ReadInt32(GetEntryOffset(id));
        }

        private void WriteEntryFlag(int id, int flag)
        {
            Contract.Requires(id < _capacity);

            _writer.Write(flag, (int)GetEntryOffset(id));
        }

        private long GetEntryOffset(int id)
        {
            return DataOffset + (EntryByteSize + sizeof(int)) * id;
        }

        #endregion // Private Methods

        #region Fields

        private List<int> _empty = new List<int>();
        private int _capacity;

        #endregion // Fields
    }
}
